#include "lib/common.h"

static int count;

static void sig_int(int signo)
{
    printf("\nreceived %d datagrams\n", count);
    exit(0);
}

//执行命令行，并返回执行标准输出ß
char *run_cmd(char *cmd)
{
    char *data = malloc(16384);
    bzero(data, sizeof(data));
    FILE *fdp;
    const int max_buffer = 256;
    char buffer[max_buffer];

    //标准I/O函数库提供了popen函数，它启动另外一个进程去执行一个shell命令行
    //r模式表示读取执行结果
    fdp = popen(cmd, "r");
    char *data_index = data;
    if (fdp)
    {
        while (!feof(fdp))
        {
            if (fgets(buffer, max_buffer, fdp) != NULL)
            {
                int len = strlen(buffer);
                memcpy(data_index, buffer, len);
                data_index += len;
            }
        }

        //pclose（）用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen（）所返回的文件指针
        pclose(fdp);
    }

    return data;
}

int main(int argc, char **argv)
{
    int listenfd;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERV_PORT);

    //closesocket（一般不会立即关闭而经历TIME_WAIT的过程）后想继续重用该socket：
    int on = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    int rt1 = bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (rt1 < 0)
    {
        //errno 是记录系统的最后一次错误代码。代码是一个int型的值，在errno.h中定义。
        //查看错误代码errno是调试程序的一个重要方法。当linux C api函数发生异常时,一般会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,
        //可以通过查看该值推测出错的原因。
        error(1, errno, "bind failed");
    }

    //第二个参数：连接请求队列(queue of pending connections)的最大长度
    int rt2 = listen(listenfd, LISTENQ);
    if (rt2 < 0)
    {
        error(1, errno, "listen failed ");
    }

    //相关知识点可参考https://blog.csdn.net/xinguan1267/article/details/17357093
    //向一个已经关闭的对端write时第一次会收到RST，若收到后继续write，会生成SIGPIPE信号，导致进程退出
    //为了避免进程退出，可捕获SIGPIPE信号，设置SIG_IGN信号处理函数，忽略它
    signal(SIGPIPE, SIG_IGN);

    int connfd;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    char buf[256];
    count = 0;

    while(1)
    {
        if ((connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len)) < 0)
        {
            error(1, errno, "bind failed");
        }

        while(1)
        {
            bzero(buf, sizeof(buf));
            int n = read(connfd, buf, sizeof(buf));
            if (n < 0)
            {
                error(1, errno, "error read message");
            }
            else if (n == 0)
            {
                //阻塞的 read 操作在完成正常接收的数据读取之后，FIN 包会通过返回一个 EOF 来完成通知，此时，read 调用返回值为 0。
                //这里强调一点，收到 FIN 包之后 read 操作不会立即返回。你可以这样理解，收到 FIN 包相当于往接收缓冲区里放置了一个 EOF 符号，之前已经在接收缓冲区的有效数据不会受到影响。
                printf("client closed \n");
                close(connfd);
                break;
            }

            count++;
            buf[n] = 0;

            if (strncmp(buf, "ls", n) == 0)
            {
                char *result = run_cmd("ls");
                //若无错误发生，send()返回所发送数据的总数（请注意这个数字可能小于len中所规定的大小）。
                //否则的话，返回SOCKET_ERROR错误，应用程序可通过WSAGetLastError()获取相应错误代码
                if (send(connfd, result, strlen(result), 0) < 0)
                {
                    return 1;
                }
                free(result);
            }
            else if (strncmp(buf, "pwd", n) == 0)
            {
                char buf[256];
                char *result = getcwd(buf, 256);
                if (send(connfd, result, strlen(result), 0) < 0)
                    return 1;
            }
            else if (strncmp(buf, "cd", 3) == 0)
            {
                char target[256];
                bzero(target, sizeof(target));
                memcpy(target, buf + 3, strlen(buf) - 3);
                if (chdir(target) == -1)
                {
                    printf("change dir failed, %s\n", target);
                }
            }
            else
            {
                char *error = "error: unknown input type";
                if (send(connfd, error, strlen(error), 0) < 0)
                {
                    return 1;
                }
            }
        }
    }

    exit(0);
}